package edu.gatech.spacetraderteam3.model;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import edu.gatech.spacetraderteam3.controller.GUIEventHandler;
import edu.gatech.spacetraderteam3.debug.Debug;
import edu.gatech.spacetraderteam3.view.ImageFilters;
import edu.gatech.spacetraderteam3.view.SGGWM;
import edu.gatech.spacetraderteam3.view.ViewBuySellOverlay;
import edu.gatech.spacetraderteam3.view.ViewMenuOverlay;
import edu.gatech.spacetraderteam3.view.ViewPlanet;
import edu.gatech.spacetraderteam3.view.ViewSystemCharacterCreation;
import edu.gatech.spacetraderteam3.view.ViewSystemEncounter;
import edu.gatech.spacetraderteam3.view.ViewSystemMap;
import edu.gatech.spacetraderteam3.view.ViewSystemTitle;
import edu.gatech.spacetraderteam3.view.ViewMenuOverlay.SGGMenuListener;

/**
 * Class used to create the player and store game info such as difficulty and other stuff
 * we haven't decided on
 * 
 * @author TBW
 * @version 1.awesomwe)
 */

public class Game {

    /**
     * A numeric constant dictating jumps before new states
     */
    private static final int JUMPS_BEFORE_NEWSTATE = 4;

    /**
     * The title screen
     */
    private final ViewSystemTitle titleView;

    /**
     * Upgrade base cost.
     */
    private static final int UPGRADE_BASE = 300;

    /**
     * Upgrade increase per Upgrade level.
     */
    private static final int UPGRADE_INCREASE = 50;

    /**
     * One hundred.
     */
    private static final int ONEHUNDRED = 100;

    /**
     * Constant by which to blur the screen.
     */
    private static final double BLURCONSTANT = .5;

    /**
     * Minimum chance to flee from an encounter.
     */
    private static final int MIN_FLEE = 35;

    /**
     * Minimum fine when police find your contraband.
     */
    private static final int MIN_FINE = 300;

    /**
     * The character creation screen
     */
    private final ViewSystemCharacterCreation creationView;

    /**
     * The Planet Surface View
     */
    private final ViewPlanet planetView;

    /**
     * The Solar System and Galaxy View
     */
    private final ViewSystemMap mapView;

    /**
     * The Pause menu
     */
    private final ViewMenuOverlay menuOverlay;

    /**
     * The buy and sell menus
     */
    private final ViewBuySellOverlay buySellOverlay;

    /**
     * The current and previous game states
     */
    private GameState gameState, prevState;

    /**
     * The current difficulty level
     */
    private Difficulty difficulty;

    /**
     * The Current Player
     */
    private Player player;

    /**
     * The Overarching Galaxy
     */
    private final SpaceGalaxy galaxy;

    /**
     * The current solar system
     */
    private SpaceSolarSystem system;

    /**
     * The current and previous planet
     */
    private SpacePlanet planet, oldPlanet;

    /**
     * The current buy prices of the current planet
     */
    private Map<Resource, Integer> buyPrices;

    /**
     * The current sell prices of the current planet
     */
    private Map<Resource, Integer> sellPrices;

    /**
     * Displays an encounter
     */
    private final ViewSystemEncounter encounterView;

    /**
     * The current encounter in the encounterView
     */
    private Encounter currentEncounter;

    /**
     * The number of jumps
     */
    private int numJumps;

    /**
     * Boolean to turn on and off the visibility of the trade windows
     */
    private boolean showTradeWindows = false;

    /**
     * Max variance in buy/sell prices (plus or minus 25 percent)
     */
    private static final double MAX_VARIANCE = .25;

    private static final int NUM_NEWS = 4;

    /**
     * Constructor for Game object. Registers itself with the GUIEventhandler,
     * instantiates the separate Views to be used later, and sets the current view to the
     * title screen.
     */
    public Game() {
        galaxy = new SpaceGalaxy();
        GUIEventHandler.setGame(this);
        menuOverlay = new ViewMenuOverlay();
        menuOverlay.setSGGMenuListener(new SGGMenuListener() {

            public void resume() {
                toggleState(getState());
                SGGWM.requestRepaint();
            }

            public boolean save() {
                return saveGame();
            }

            public void quit() {
                Debug.write("GOING DOWN NOW", 1);
                System.exit(0);
            }
        });

        titleView = new ViewSystemTitle();
        creationView = new ViewSystemCharacterCreation();
        planetView = new ViewPlanet();
        buySellOverlay = new ViewBuySellOverlay();
        mapView = new ViewSystemMap(getGalaxy());
        encounterView = new ViewSystemEncounter();

        titleView.setAction(true);
        gameState = GameState.TITLE;
        prevState = gameState;
        SGGWM.setWindowGroup(gameState);
        numJumps = 0;
        galaxy.randomizeState();

        /* Testing code goes here */

        // startGame("derp", new int[] { 0, 0, 0, 0 }, Difficulty.EASY);
        // toggleMenu();
        // toggleState(GameState.ENCOUNTER);

        // ArrayList<EncounterAction> derp = new ArrayList<EncounterAction>();
        // derp.add(EncounterAction.ATTACK);
        // encounterView.setActions(derp);
    }

    /**
     * Getter for player object.
     * 
     * @return The current player.
     */
    public Player getPlayer() {
        return player;
    }

    /**
     * Setter for player object.
     * 
     * @param p
     *            The player you're setting to (probably due to loading).
     */
    public void setPlayer(Player p) {
        this.player = p;
    }

    /**
     * Shows menu that appears when you press escape.
     */
    public void toggleMenu() {
        menuOverlay.hideSaveMessage();
        if (gameState != GameState.CHARACTER_CREATION && gameState != GameState.TITLE) {
            toggleState(GameState.OVERLAY);
        }
        SGGWM.requestRepaint();
    }

    /**
     * Getter for GameState.
     * 
     * @return The current Gamestate.
     */
    public GameState getState() {
        return gameState;
    }

    /**
     * Shows the title screen of the game. Used for when game is first started, or when
     * back button is pressed on character creation.
     */
    public void showTitle() {
        toggleState(GameState.TITLE);
    }

    /**
     * Attempts to save the current Player object. Returns true if successful, false
     * otherwise.
     * 
     * @return true if player was saved, false if error occured.
     */
    public boolean saveGame() {
        return FileManager.saveGame(player);
    }

    /**
     * Attempts to load the Player object from file. If a problem occurs, shows an error
     * message.
     */
    public void continueGame() {
        player = FileManager.loadGame();
        if (player == null) {
            titleView.showNoSaveMessage();
        } else {
            arriveAtPlanet(player.getPlanet());
            planetView.setWelcomeMessage("Welcome back, " + player.getName() + "!");
        }
    }

    /**
     * Shows character creation screen. Only to be called from title screen
     */
    public void newGame() {
        toggleState(GameState.CHARACTER_CREATION);
        creationView.reset();
        titleView.setAction(false);
        creationView.setAction(true);
    }

    /**
     * Makes a new player and sets the game's difficulty
     * 
     * @param name
     *            The player's name
     * @param stats
     *            The stats the player has chosen
     * @param difficulty
     *            The difficulty of the game
     */
    public void startGame(String name, int[] stats, Difficulty difficulty) {
        player = new Player(name, stats);
        this.difficulty = difficulty;
        Debug.write("With difficulty: " + difficulty, Debug.EVENTS);
        system = galaxy.getSystemFromIndex(5);
        player.setPlanet(system.getPlanets()[0]);
        arriveAtPlanet(system.getPlanets()[0]);
    }

    /**
     * gets the difficulty of the game
     * 
     * @return the difficulty
     */
    public Difficulty getDifficulty() {
        return difficulty;
    }

    /**
     * Getter for the current SpaceGalaxy.
     * 
     * @return The galaxy the player is in. Kinda silly considering there's only one.
     */
    public SpaceGalaxy getGalaxy() {
        return galaxy;
    }

    // ////////////////////////////////////////////////
    // Encounter stuff //
    // ////////////////////////////////////////////////
    /**
     * Called when you attempt to travel to a new planet.
     * 
     * @param destination
     *            The SpacePlanet you're heading to.
     */
    public void doEncounter(SpacePlanet destination) {
        showTradeWindows = false;
        oldPlanet = destination;
        currentEncounter = EncounterFactory.generateEncounter();
        encounterView.setEncounter(currentEncounter);
        toggleState(GameState.ENCOUNTER);
    }

    /**
     * Called when the encounter is finished. Heals the Player's ship and calls
     * arriveAtPlanet.
     */
    public void encounterOver() {
        player.getShip().fullHeal();
        // encounterView.setMessageWindow(true);
        arriveAtPlanet(oldPlanet);
    }

    /**
     * Initiates trade with a space-bound trader.
     */
    public void trade() {
        encounterView.hideChoices();
        buyPrices = currentEncounter.getActor().getPrices();
        sellPrices = currentEncounter.getActor().getSellPrices();
        final Map<Resource, int[]> pricesAndWeights = new HashMap<Resource, int[]>();
        for (Resource r : Resource.values()) {
            if (sellPrices.containsKey(r)) {
                buyPrices
                        .put(r,
                             buyPrices.containsKey(r) ? (int) (buyPrices.get(r) * (1.0 + (MAX_VARIANCE / (player
                                     .getTrading() + 1)))) : -1);
                sellPrices.put(r,
                               (int) (sellPrices.get(r) * (1.0 - (MAX_VARIANCE / (player
                                       .getTrading() + 1)))));
                pricesAndWeights.put(r, new int[] { buyPrices.get(r), sellPrices.get(r),
                        r.weight() });
            }
        }
        buySellOverlay.setCredits(player.getCredits());
        buySellOverlay.initialize(pricesAndWeights, player.getCurrentWeight());
        buySellOverlay.refreshBuyLabels();
        buySellOverlay.refreshSellLabels();
        showTradeWindows(GameState.ENCOUNTER);
    }

    /**
     * Allows the police to board your ship and check for contraband.
     */
    public void encounterPoliceBoard() {
        if (player.getInventory().hasContraband()) {
            Debug.write("Police status: Hostile", Debug.VERBOSE);
            int fine = MIN_FINE;
            if (player.getCredits() < MIN_FINE) {
                fine = player.getCredits();
            }
            encounterView
                    .setMessage("The police confiscate your contriband and fine you "
                            + fine + " credits.");
            if (player.removeResource(Resource.FIREARMS, player.getInventory()
                    .getResource(Resource.FIREARMS))
                    && player.removeResource(Resource.NARCOTICS, player.getInventory()
                            .getResource(Resource.NARCOTICS))) {
                Debug.write("Removed player's contraband.", Debug.USER);
            }
            player.removeCredits(fine);
            System.out.println("Fined!");
            encounterOver();
        } else {
            encounterView
                    .setMessage("The police search your ship. Finding no contraband, they let you on your way.");
            encounterOver();
        }
    }

    /**
     * Allows the player to attack the Actor in the current encounter (police, trader, or
     * pirates)
     */
    public void encounterAttack() {
        final Ship enemyShip = currentEncounter.getActor().getShip();
        encounterView.setMessage("You open fire on the "
                + currentEncounter.getActor().getFaction() + " ship!");
        System.out.println("----------------ENEMY SHIP'S STATS--------------------");
        int attack = player.getShip().attack(currentEncounter.getActor().getShip(),
                                                   player.getFighting(), 0, 0);
        if (attack <= 0) {
            encounterView.setMessage("You miss!");
            System.out.println("Enemy ship's "
                    + currentEncounter.getActor().getShip().getStatus());
            encounterEnemyAction("You miss!             ");
        } else {
            encounterView.setMessage("You hit for " + attack + " damage!");
            System.out.println("Enemy ship's "
                    + currentEncounter.getActor().getShip().getStatus());
            encounterEnemyAction(("You hit for " + attack + " damage!"));
        }
        if (enemyShip.getHealth() <= 0) {
            encounterLoot(currentEncounter.getActor());
            System.out.println("The enemy has been defeated and you loot thier ship.");
            encounterOver();
        }
    }

    /**
     * Called when the player is under attack from the enemy ship.
     * 
     * @param playerTurn
     *            - A string indicating the player's turn
     */
    public void encounterEnemyAction(String playerTurn) {
        Debug.write("----------------PLAYER SHIP'S STATS--------------------", Debug.USER);
        int attack = currentEncounter
                .getActor()
                .getShip()
                .attack(player.getShip(), player.getFighting(), player.getEngineering(),
                        player.getPiloting());
        if (attack <= 0) {
            attack = 0;
            setMessage(playerTurn, "The enemy ship misses!");
            System.out.println("Player ship's " + player.getShip().getStatus());
        } else {
            setMessage(playerTurn, "The enemy hits for " + attack + " damage!");
            System.out.println("Player ship's " + player.getShip().getStatus());
        }
        if (player.getShip().getHealth() <= 0) {
            setMessage(null, "YOU DIE");
        }
    }

    /**
     * Sets the encounterView message window
     * 
     * @param playerTurn
     *            Information from the player's turn
     * @param enemyTurn
     *            Information from the enemy's turn
     */
    public void setMessage(String playerTurn, String enemyTurn) {
        if (playerTurn == null) {
            encounterView.setMessage(enemyTurn);
            return;
        } else
            encounterView.setMessage(playerTurn + "              " + enemyTurn);
    }

    /**
     * Allows the user to flee from the encounter (to keep police from boarding or pirates
     * from attacking)
     */
    public void encounterFlee() {
        if (Math.random() * ONEHUNDRED < (MIN_FLEE + (player.getPiloting() * 4))) {
            encounterOver();
            planetView
                    .setWelcomeMessage("You successfully pilot your ship through the heavy fire to safety! You have "
                            + player.getFuel() + " fuel left.");
        } else {
            encounterEnemyAction(null);
        }
    }

    /**
     * Stub method, currently. For taking the enemies' inventory and adding it to your
     * own.
     */
    public void encounterLoot(Actor enemy) {
        Inventory enemyInventory = enemy.getInventory();
        for (Resource res : Resource.values()) {
            int numRes = enemyInventory.getResource(res);
            player.addResource(res, numRes);
        }

        planetView.setPlayerAmount(player.getItems());
        buySellOverlay.setPlayerAmount(player.getItems());
        
        Debug.write("Enemy ship looted.", Debug.EVENTS);
    }

    /**
     * Allows the pirates to board your ship and take half your stuff.
     */
    public void encounterPiratesBoard() {
        player.becomeLooted();
        encounterView
                .setMessage("You let the pirates on board and watch by as they take many of your hard-earned goods and credits. They spare your life... this time.");
        encounterOver();
    }

    // ////////////////////////////////////////////////
    // Paint stuff //
    // ////////////////////////////////////////////////
    /**
     * Paints the current game view to the screen.
     * 
     * @param g
     *            Graphics object to be painted with (passed from JPanel somewhere
     * @param wid
     *            Width of screen to draw to
     * @param hei
     *            Height of screen to draw to
     */
    public void paint(Graphics2D g, int wid, int hei) {
        final BufferedImage buffer = new BufferedImage(wid, hei,
                BufferedImage.TYPE_INT_ARGB);
        final Graphics2D g2 = (Graphics2D) buffer.getGraphics();
        switch ((gameState == GameState.OVERLAY ? prevState : gameState)) {
            case TITLE:
                titleView.paint(g2);
                break;
            case CHARACTER_CREATION:
                creationView.paint(g2);
                break;
            case MAP:
                mapView.paint(g2);
                break;
            case PLANET:
                planetView.paint(g2);
                break;
            case ENCOUNTER:
                encounterView.paint(g2);
                break;
            default:
                g2.setColor(Color.BLACK);
                final String awkward = "Well. This is awkward.";
                final int stringWidth = g2.getFontMetrics().stringWidth(awkward);
                g2.drawString(awkward, (wid >> 1) - (stringWidth >> 1), (hei >> 1));
                break;
        }
        if (showTradeWindows) {
            buySellOverlay.paint(g2);
        }
        if (gameState == GameState.OVERLAY) {
            g2.setColor(new Color(0, 0, 0, ONEHUNDRED));
            g2.fillRect(0, 0, wid, hei);
            if (g2.drawImage(ImageFilters.fastBlur(buffer, BLURCONSTANT), 0, 0, null)) {
                Debug.write("Drew blur!", Debug.VERBOSE);
            }
            menuOverlay.paint(g2);
            g2.dispose();
        }
        g.drawImage(buffer, 0, 0, null);
        g2.dispose();
    }

    /**
     * Tells the MapView which systems the user can travel to. Helper method, called by
     * toggleState.
     */
    private void updateVisitableSystems() {
        final Map<SpaceSolarSystem, Boolean> travel = new HashMap<SpaceSolarSystem, Boolean>();
        final List<SpaceSolarSystem> systems = galaxy.getSystems();
        for (SpaceSolarSystem s : systems) {
            travel.put(s, player.canFlyTo(s));
        }
        mapView.setVisitableSystems(travel);
    }

    /**
     * Sets the gamestate and makes it so you can't interact with invisible gamestates.
     * 
     * @param gameState
     *            State you're switching to
     */
    public void toggleState(GameState gameState) {
        showTradeWindows = false;
        if (this.gameState == gameState) {
            toggleAction(gameState, false);
            this.gameState = prevState;
            toggleAction(this.gameState, true);
        } else {
            prevState = this.gameState;
            toggleAction(prevState, false);
            this.gameState = gameState;
            toggleAction(gameState, true);
        }
        SGGWM.setWindowGroup(this.gameState);
        if (this.gameState == GameState.MAP) {
            updateVisitableSystems();
        }
    }

    /**
     * Toggles the action so you don't interact with underlying windows.
     * 
     * @param state
     *            State you're going to
     * @param toggle
     *            Whether or not to toggle
     */
    private void toggleAction(GameState state, boolean toggle) {
        switch (state) {
            case PLANET:
                showTradeWindows = toggle;
                break;
            case CHARACTER_CREATION:
                creationView.setAction(toggle);
                break;
            case MAP:
                mapView.setAction(toggle);
                break;
            case TITLE:
                titleView.setAction(toggle);
                break;
            case ENCOUNTER:
                encounterView.setAction(toggle);
                break;
            default:
                break;
        }
    }

    /**
     * Method for buying goods from the planet player is docked on.
     * 
     * @param r
     *            Resource player is buying
     * @param n
     *            Amount of r player is buying
     * @return True if goods were bought and money was decremented correctly - false if
     *         goods could not be bought.
     */
    public boolean buyGoods(Resource r, int n) {
        final int price = buyPrices.containsKey(r) ? buyPrices.get(r) : 0;// TODO: error
                                                                          // dialog goes
                                                                          // here

        if (!(price * n < player.getCredits())) {
            buySellOverlay.notEnoughMoney();
            return false;
        } else if (!player.canHold(r, n)) {
            buySellOverlay.notEnoughCargoSpace();
            return false;
        } else if (this.gameState == GameState.ENCOUNTER) {
            if (currentEncounter.getActor().removeResource(r, n)) {
                if (player.addResource(r, n)) {
                    Debug.write("Player succesfully added to inventory", Debug.EVENTS);
                }
                Debug.write("Player's credits: " + player.getCredits()
                                    + " price of purchase:" + price * n
                                    + " Weight of purchase: " + r.weight() * n,
                            Debug.VERBOSE);
                player.removeCredits(price * n);
                Debug.write("Player's credits now: " + player.getCredits(), Debug.VERBOSE);
                planetView.setCredits(player.getCredits());
                planetView.setPlayerAmount(player.getItems());
                buySellOverlay.setCredits(player.getCredits());
                buySellOverlay.setPlayerAmount(player.getItems());
                buySellOverlay
                        .setWeight(player.getMaxWeight(), player.getCurrentWeight());
                buySellOverlay.successfulTrade();
                return true;
            } else {
                Debug.write("Could not buy from Trader - trader out of goods.",
                            Debug.VERBOSE);
                return false;
            }
        } else if (gameState == GameState.PLANET && planet.removeResource(r, n)) {
            player.addResource(r, n);
            Debug.write("Player's credits: " + player.getCredits()
                                + " price of purchase:" + price * n
                                + " Weight of purchase: " + r.weight() * n, Debug.VERBOSE);
            player.removeCredits(price * n);
            Debug.write("Player's credits now: " + player.getCredits(), Debug.VERBOSE);
            planetView.setCredits(player.getCredits());
            planetView.setPlayerAmount(player.getItems());
            buySellOverlay.setCredits(player.getCredits());
            buySellOverlay.setPlayerAmount(player.getItems());
            buySellOverlay.setWeight(player.getMaxWeight(), player.getCurrentWeight());
            buySellOverlay.successfulTrade();
        }
        return false;
    }

    /**
     * Method for selling goods to the planet player is docked on.
     * 
     * @param r
     *            Resource in question.
     * @param n
     *            Amount of r player is selling.
     * @return True if the player sold the resources correctly, false if the transaction
     *         did not go through.
     */
    public boolean sellGoods(Resource r, int n) {
        Debug.write("Game trying to remove " + n + " " + r, Debug.VERBOSE);
        final int price = sellPrices.containsKey(r) ? sellPrices.get(r) : 0;// TODO: error
                                                                            // dialog goes
                                                                            // here
        if (player.removeResource(r, n)) {
            player.addCredits(price * n);
            planetView.setCredits(player.getCredits());
            planetView.setPlayerAmount(player.getItems());
            planetView.setCredits(player.getCredits());
            buySellOverlay.setCredits(player.getCredits());
            buySellOverlay.setPlayerAmount(player.getItems());
            buySellOverlay.setWeight(player.getMaxWeight(), player.getCurrentWeight());
            buySellOverlay.successfulTrade();
            return true;
        }
        buySellOverlay.notEnoughItemToSell();
        return false;
    }

    /**
     * Contains logic for moving player to a new Planet. Sets appropriate data and updates
     * View accordingly.
     * 
     * @param p
     *            Planet player is docking upon.
     */
    public void arriveAtPlanet(SpacePlanet p) {
        if (numJumps++ == JUMPS_BEFORE_NEWSTATE) {
            numJumps = 0;
            this.galaxy.randomizeState();
        }
        planet = p;
        player.useFuel(p.getSystem());
        player.setPlanet(p);
        buyPrices = p.getPrices();
        sellPrices = p.getSellPrices();
        final Map<Resource, int[]> pricesAndWeights = new HashMap<Resource, int[]>();
        for (Resource r : Resource.values()) {
            if (sellPrices.containsKey(r)) {
                buyPrices
                        .put(r,
                             buyPrices.containsKey(r) ? (int) (buyPrices.get(r) * (1.0 + (MAX_VARIANCE / (player
                                     .getTrading() + 1)))) : -1);
                sellPrices.put(r,
                               (int) (sellPrices.get(r) * (1.0 - (MAX_VARIANCE / (player
                                       .getTrading() + 1)))));
                pricesAndWeights.put(r, new int[] { buyPrices.get(r), sellPrices.get(r),
                        r.weight() });
            }
        }
        planetView.setWelcomeMessage("Welcome to " + p.getName()
                + "!                          You have " + player.getFuel()
                + " fuel left.");
        planetView.setPlayerAmount(player.getItems());
        buySellOverlay.initialize(pricesAndWeights, player.getCurrentWeight());
        buySellOverlay.setCredits(player.getCredits());
        Set<SpaceSolarSystem> set = galaxy.getRandSystems(NUM_NEWS);
        String news = "";
        for (SpaceSolarSystem sys : set) {
            news += NewsTranslator.translateState(sys) + " \n";
        }
        planetView.initialize(pricesAndWeights, player.getCurrentWeight(), news);
        updateShipInfo();
        // planetView.setShipInfo(fuel, type);
        buySellOverlay.refreshBuyLabels();
        buySellOverlay.refreshSellLabels();
        // planetView.refreshShipLabels();
        // planetView.initializeProducts(buyPrices, sellPrices);
        planetView.setPlanetPaintDelegate(p.getPlanetPaintDelegate());
        planetView.setCredits(player.getCredits());
        // planetView.setCredits(getCredits());
        this.system = p.getSystem();
        mapView.setPlanet(planet);
        mapView.setSystem(system);
        toggleState(GameState.PLANET);
        showTradeWindows(GameState.PLANET);
        // gameState = GameState.PLANET;
    }

    public void sendPlanetInfo(SpacePlanet plan) {
        double tempRatio = 0.0;
        Resource tempRes = Resource.WATER;
        for (Resource res : Resource.values()) {
            if (plan.getResourcePrice(res) / res.basePrice() > tempRatio) {
                tempRatio = plan.getResourcePrice(res) / res.basePrice();
                tempRes = res;
            }
        }
        mapView.setPlanetInfo(plan.getName(), plan.getTechLevel(), plan.getGovernment(),
                              tempRes.toString());
    }

    /**
     * Shows the trade windows overlay.
     * 
     * @param listenState
     *            Gamestate to listen to.
     */
    public void showTradeWindows(GameState listenState) {
        showTradeWindows = true;
        if (listenState == GameState.PLANET) {
            buySellOverlay.showLeaveButton(false);
        } else {
            buySellOverlay.showLeaveButton(true);
        }
        buySellOverlay.setListeningMode(listenState);
    }

    /**
     * Different difficulties of the game
     * 
     * @author Someone That's Not Randal (But I'm javadocing yo)
     * @version 1.awesome
     */
    public enum Difficulty {
        VERY_EASY("Very Easy"),
        EASY("Easy"),
        NORMAL("Normal"),
        HARD("Hard"),
        IMPOSSIBLE("Impossible");

        /**
         * The string shown on the drop down box
         */
        private final String displayName;

        /**
         * Constructor that sets the string representation of the difficulty
         * 
         * @param displayName
         *            the display name of the difficulty!
         */
        private Difficulty(String displayName) {
            this.displayName = displayName;
        }

        /**
         * Gets the string representation of the difficulty
         * 
         * @return the string representation of the difficulty
         */
        public String toString() {
            return displayName;
        }
    }

    /*
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * KEEP THESE CHANGES!!!!!!!!!!!!!!!!!!!!!!!!!!!
     */
    /**
     * Refuels the player's ship.
     */
    public void refuel() {
        player.reFuel();
        updateShipInfo();
        planetView.setCredits(player.getCredits());
    }

    /**
     * Upgrades the player's ship's shield.
     */
    public void upgradeShield() {
        final int price = UPGRADE_BASE + UPGRADE_INCREASE * player.getShip().getShield();
        if (player.getCredits() >= price
                && player.getShip().getShield() < ShipShield.getMaxLevel()) {
            player.removeCredits(price);
            player.getShip().upgradeShield();
        }
        updateShipInfo();
        planetView.setCredits(player.getCredits());
    }

    /**
     * Upgrades the player's ship's weapon.
     */
    public void upgradeWeapon() {
        final int price = UPGRADE_BASE + UPGRADE_INCREASE * player.getShip().getWeapon();
        if (player.getCredits() >= price
                && player.getShip().getWeapon() < ShipWeapon.getMaxLevel()) {
            player.removeCredits(price);
            player.getShip().upgradeWeapon();
        }
        updateShipInfo();
        planetView.setCredits(player.getCredits());
    }

    /**
     * Upgrades the player's ship's armor.
     */
    public void upgradeArmor() {
        final int price = UPGRADE_BASE + UPGRADE_INCREASE * player.getShip().getArmor();
        if (player.getCredits() >= price
                && player.getShip().getArmor() < ShipArmor.getMaxLevel()) {
            player.removeCredits(price);
            player.getShip().upgradeArmor();
        }
        updateShipInfo();
        planetView.setCredits(player.getCredits());
    }
    
    public void upgradeCargo(){
        final int price = UPGRADE_BASE + 700 +  ((player.getShip().getMaxCargoWeight() - 1000)/500)* UPGRADE_INCREASE;
        if (player.getCredits() >= price){
            player.removeCredits(price);
            player.getShip().upgradeMaxCargoWeight();
        }
        updateShipInfo();
        planetView.setCredits(player.getCredits());
        buySellOverlay.setWeight(player.getMaxWeight(), player.getCurrentWeight());
    }

    /*
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * THE CHANGES ABOVE THIS! KEEP THEM!
     */
    /**
     * Sends the (probably recently modified) ship info to the View.
     */
    public void updateShipInfo() {
        final Ship playerShip = player.getShip();
        // String name, String type, int[4] : fuel, shield, weapon, armor, int[4] : prices
        final int[] info = new int[] { playerShip.getCurrentFuel(),
                playerShip.getShield(), playerShip.getWeapon(), playerShip.getArmor(),
                playerShip.getMaxCargoWeight()};
        planetView.setShipInfo(playerShip.getName(), playerShip.getType(), info,
                               upgradePrices(info));
    }

    /**
     * Helper method that calculates the cost to upgrade a particular piece of the
     * player's ship.
     * 
     * @param arr
     *            Ship stats
     * @return Int array that corresponds to prices for fuel, shields, weapons, and armor,
     *         in that order.
     */
    public int[] upgradePrices(int[] arr) {
        final int[] prices = new int[arr.length];
        prices[0] = player.calcFuelCost();
        for (int i = 1; i < (prices.length - 1); i++) {
            prices[i] = UPGRADE_BASE + (UPGRADE_INCREASE * arr[i]);
        }
        prices[prices.length-1] = UPGRADE_BASE + 700 +  ((player.getShip().getMaxCargoWeight() - 1000)/500)* UPGRADE_INCREASE;        return prices;
    }

    /**
     * Game object String representation.
     * 
     * @return difficulty, player, and planet objects.
     */
    public String toString() {
        return "Game object with Difficulty " + this.difficulty + ", Player " + player
                + " on Planet " + this.planet;
    }
}
